﻿namespace Contoso.ActiveDirectory {

    #region Usings
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.DirectoryServices;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    #endregion

    public abstract class UserComputerMethods {

        /// <summary>
        /// Disables an account
        /// </summary>
        /// <param name="account">The account to disable</param>
        /// <returns>True if successful</returns>
        public virtual bool DisableAccount<T>(T account) where T : ADBaseObjectProperties, IUserComputer {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} for DN: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), ((account != null) && !string.IsNullOrWhiteSpace(account.DN)) ? account.DN : "NA"));

            if (account == null) {
                throw new ArgumentNullException("account");
            }
            if (string.IsNullOrWhiteSpace(account.DN)) {
                throw new ArgumentNullException("account.DN");
            }

            return this.AddUserAccountControlFlag(account, UserAccountControlFlags.AccountDisabled);
        }

        /// <summary>
        /// Enables an account
        /// </summary>
        /// <param name="account">The account to enable</param>
        /// <returns>True if successful</returns>
        public virtual bool EnableAccount<T>(T account) where T : ADBaseObjectProperties, IUserComputer {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} for DN: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), ((account != null) && !string.IsNullOrWhiteSpace(account.DN)) ? account.DN : "NA"));

            if (account == null) {
                throw new ArgumentNullException("account");
            }
            if (string.IsNullOrWhiteSpace(account.DN)) {
                throw new ArgumentNullException("account.DN");
            }

            return this.RemoveUserAccountControlFlag(account, UserAccountControlFlags.AccountDisabled);
        }

        #region UAC Add/Remove Flags methods
        /// <summary>
        /// Adds the specified flag from the UserAccountControl attribute
        /// </summary>
        /// <param name="account">The account to add the flag</param>
        /// <param name="flag">The flag to add</param>
        /// <returns>True if successful</returns>
        protected bool AddUserAccountControlFlag<T>(T account, UserAccountControlFlags flag) where T : ADBaseObjectProperties, IUserComputer {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} for DN: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), ((account != null) && !string.IsNullOrWhiteSpace(account.DN)) ? account.DN : "NA"));

            #region Validation
            if (account == null) {
                throw new ArgumentNullException("account");
            }
            if (string.IsNullOrWhiteSpace(account.DN)) {
                throw new ArgumentNullException("account.DN");
            }
            if (flag == UserAccountControlFlags.NotSet) {
                throw new InvalidOperationException(string.Format("Invalid UAC Flag: {0}", flag));
            }
            #endregion

            var methodParameters = new MethodParameters() {
                ServerNameOrAdjustedBindPath = null,
                ObjectDN = account.DN.Trim()
            };

            return ConnectionManager.DirectoryServerHandler<MethodParameters, bool>(
                    ref methodParameters,
                    (x) => {
                        methodParameters.ServerNameOrAdjustedBindPath = ADDataAccess.GetAdjustedBindPath(forupdate: true, bindPath: methodParameters.ObjectDN);

                        #region Validation
                        if (!ADDataAccess.ObjectExists(methodParameters.ServerNameOrAdjustedBindPath)) {
                            throw new ApplicationException(string.Format("Path does not exist: {0}", methodParameters.ServerNameOrAdjustedBindPath));
                        }
                        #endregion

                        using (var directoryEntry = new DirectoryEntry(methodParameters.ServerNameOrAdjustedBindPath, null, null, methodParameters.AuthenticationTypes)) {
                            
                            if (!directoryEntry.Properties.Contains("userAccountControl")) {
                                throw new InvalidOperationException("Not performing action due to it does not contain a userAccountControl attribute.");
                            }

                            UserAccountControlFlags uac = UserAccountControlFlags.NotSet;
                            if (Enum.TryParse<UserAccountControlFlags>(directoryEntry.Properties["userAccountControl"].Value.ToString(), ignoreCase: true, result: out uac)) {
                                var newUac = uac;
                                newUac |= flag;

                                Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} DN: {3} Previous UAC: {4}; New UAC: {5}",
                                    DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(),
                                    account.DN, uac, newUac));

                                directoryEntry.Properties["userAccountControl"].Value = (int)newUac;
                                directoryEntry.CommitChanges();

                                return true;
                            }
                            else {
                                throw new InvalidOperationException("Unable to parse userAccountControl attribute.");
                            }
                        }
                    });
        }

        /// <summary>
        /// Removes the specified flag from the UserAccountControl attribute
        /// </summary>
        /// <param name="account">The account to remove the flag</param>
        /// <param name="flag">The flag to remove</param>
        /// <returns>True if successful</returns>
        protected bool RemoveUserAccountControlFlag<T>(T account, UserAccountControlFlags flag) where T : ADBaseObjectProperties, IUserComputer {
            Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} for DN: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), ((account != null) && !string.IsNullOrWhiteSpace(account.DN)) ? account.DN : "NA"));

            #region Validation
            if (account == null) {
                throw new ArgumentNullException("account");
            }
            if (string.IsNullOrWhiteSpace(account.DN)) {
                throw new ArgumentNullException("account.DN");
            }
            if (flag == UserAccountControlFlags.NotSet) {
                throw new InvalidOperationException(string.Format("Invalid UAC Flag: {0}", flag));
            }
            #endregion

            var methodParameters = new MethodParameters() {
                ServerNameOrAdjustedBindPath = null,
                ObjectDN = account.DN.Trim()
            };

            return ConnectionManager.DirectoryServerHandler<MethodParameters, bool>(
                    ref methodParameters,
                    (x) => {
                        methodParameters.ServerNameOrAdjustedBindPath = ADDataAccess.GetAdjustedBindPath(forupdate: true, bindPath: methodParameters.ObjectDN);

                        #region Validation
                        if (!ADDataAccess.ObjectExists(methodParameters.ServerNameOrAdjustedBindPath)) {
                            throw new ApplicationException(string.Format("Path does not exist: {0}", methodParameters.ServerNameOrAdjustedBindPath));
                        }
                        #endregion

                        using (var directoryEntry = new DirectoryEntry(methodParameters.ServerNameOrAdjustedBindPath, null, null, methodParameters.AuthenticationTypes)) {
                            if (!directoryEntry.Properties.Contains("userAccountControl")) {
                                throw new InvalidOperationException("Not performing action due to it does not contain a userAccountControl attribute.");
                            }

                            UserAccountControlFlags uac = UserAccountControlFlags.NotSet;
                            if (Enum.TryParse<UserAccountControlFlags>(
                                directoryEntry.Properties["userAccountControl"].Value.ToString(), ignoreCase: true, result: out uac)) {
                                var newUac = uac;
                                newUac &= ~flag;

                                Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} DN: {3} Previous UAC: {4}; New UAC: {5}",
                                    DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(),
                                    account.DN, uac, newUac));

                                directoryEntry.Properties["userAccountControl"].Value = (int)newUac;
                                directoryEntry.CommitChanges();

                                return true;
                            }
                            else {
                                throw new InvalidOperationException("Unable to parse userAccountControl attribute.");
                            }
                        }
                    });
        }
        #endregion
    }
}
